package com.gmail.dengtao.joe.group.member;

import com.gmail.dengtao.joe.group.transport.ChannelReceiver;
import com.gmail.dengtao.joe.transport.session.Session;

/**
 * The Member interface, defines a member in the group.
 * Each member can carry a set of properties, defined by the actual implementation.<BR>
 * A member is identified by the host/ip/uniqueId<br>
 * The host is what interface the member is listening to, to receive data<br>
 * The port is what port the member is listening to, to receive data<br>
 * The uniqueId defines the session id for the member. This is an important feature
 * since a member that has crashed and the starts up again on the same port/host is
 * not guaranteed to be the same member, so no state transfers will ever be confused
 * @author <a href="mailto:joe.dengtao@gmail.com">DengTao</a>
 * @version 1.0
 * @since 1.0
 */
public class Member {

	public interface Event {
		/** >>>{@link Status#WAITE_HELLO} */
		public static final int SENDER_CONNECTED = 1;
		/** >>>{@link Status#SENDER_OK} */
		public static final int SENDER_HELLO_RECEIVED = 2;
		/** >>>{@link Status#SENDER_NOTCONNECT} */
		public static final int SENDER_DISCONNECTED = 240;
		
		/** >>>{@link Status#RECEIVER_CONNECTED} */
		public static final int RECEIVER_CONNECTED = 16;
		/** >>>{@link Status#SEND_HELLO} */
		public static final int RECEIVER_SEND_HELLO = 32;
		/** >>>{@link Status#RECEIVER_OK} */
		public static final int RECEIVER_HELLO_SENT = 64;
		/** >>>{@link Status#RECEIVER_NOTCONNECT} */
		public static final int RECEIVER_DISCONNECTED = 15;
	}

	public enum Status {
		SENDER_NOT_CONNECT,
		SENDER_WAITE_HELLO,
		SENDER_OK,
		
		RECEIVER_NOT_CONNECT,
		RECEIVER_CONNECTED,
		RECEIVER_SEND_HELLO,
		RECEIVER_OK,
		
		AVAILABLE
	}

	private static final int SENDER_NOT_CONNECT_MASK = 15;
	private static final int SENDER_WAITE_HELLO_MASK = 15;
	private static final int SENDER_OK_MASK = 15;
	
	private static final int RECEIVER_NOT_CONNECT_MASK = 240;
	private static final int RECEIVER_CONNECT_MASK = 240;
	private static final int RECEIVER_SEND_HELLO_MASK = 240;
	private static final int RECEIVER_OK_MASK = 240;
	
	private static final int MEMBER_AVAILABLE_MASK = 115;
	
	private String host;
	private int port;	// listen port
	private String localUuid;
	private String remoteUuid;
	private Session sender;
	private Session receiver;
	
	private int status = 0;

	private final Object statusLock = new Object();
	
    public Member() {
		super();
	}

	public Member(String host, int port) {
		super();
		this.host = host;
		this.port = port;
	}

	public boolean hasStatus(Status status) {
		synchronized (statusLock) {
			switch (status) {
				case SENDER_NOT_CONNECT:
					return (this.status & SENDER_NOT_CONNECT_MASK) == 0;
			
				case SENDER_WAITE_HELLO:
					return (this.status & SENDER_WAITE_HELLO_MASK) == 1;
					
				case SENDER_OK:
					return (this.status & SENDER_OK_MASK) == 3;
					
				case RECEIVER_NOT_CONNECT:
					return (this.status & RECEIVER_NOT_CONNECT_MASK) == 0;
		
				case RECEIVER_CONNECTED:
					return (this.status & RECEIVER_CONNECT_MASK) == 16;
					
				case RECEIVER_SEND_HELLO:
					return (this.status & RECEIVER_SEND_HELLO_MASK) == 48;
					
				case RECEIVER_OK:
					return (this.status & RECEIVER_OK_MASK) == 112;
					
				case AVAILABLE:
					return (this.status & MEMBER_AVAILABLE_MASK) == 115;
					
				default:
					return false;
			}
		}
	}

	public String getLocalUuid() {
		return localUuid;
	}

	public void setLocalUuid(String localUuid) {
		this.localUuid = localUuid;
	}

	public String getRemoteUuid() {
		return remoteUuid;
	}

	public void setRemoteUuid(String remoteUuid) {
		this.remoteUuid = remoteUuid;
	}

	public Session getSender() {
		return sender;
	}

	public void setSender(Session sender) {
		this.sender = sender;
	}

	public Session getReceiver() {
		return receiver;
	}

	public void setReceiver(Session receiver) {
		this.receiver = receiver;
	}

	/**
     * Returns the listen host for the ChannelReceiver implementation
     * @return IPv4 or IPv6 representation of the host address this member listens to incoming data
     * @see ChannelReceiver
     */
    public String getHost() {
    	return host;
    }

    /**
     * Returns the listen port for the ChannelReceiver implementation
     * @return the listen port for this member, -1 if its not listening on an insecure port
     * @see ChannelReceiver
     */
	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	public void setHost(String host) {
		this.host = host;
	}

	public void addEvent(int event) {
		synchronized (statusLock) {
			switch (event) {
				case Event.SENDER_CONNECTED:
					if (hasStatus(Status.SENDER_NOT_CONNECT)) {
						status |= event;
					} else {
						throw new IllegalStateException(String.valueOf(event));
					}
					break;
		
				case Event.SENDER_HELLO_RECEIVED:
					if (hasStatus(Status.SENDER_WAITE_HELLO)) {
						status |= event;
					} else {
						throw new IllegalStateException(String.valueOf(event));
					}
					break;
				
				case Event.SENDER_DISCONNECTED:
					if (hasStatus(Status.SENDER_WAITE_HELLO) || hasStatus(Status.SENDER_OK)) {
						status &= event; 
					} else {
						throw new IllegalStateException(String.valueOf(event));
					}
					break;
					
				case Event.RECEIVER_CONNECTED:
					if (hasStatus(Status.RECEIVER_NOT_CONNECT)) {
						status |= event;
					} else {
						throw new IllegalStateException(String.valueOf(event));
					}
					break;
				
				case Event.RECEIVER_SEND_HELLO:
					if (hasStatus(Status.RECEIVER_CONNECTED)) {
						status |= event;
					} else {
						throw new IllegalStateException(String.valueOf(event));
					}
					break;
					
				case Event.RECEIVER_HELLO_SENT:
					if (hasStatus(Status.RECEIVER_SEND_HELLO)) {
						status |= event;
					} else {
						throw new IllegalStateException(String.valueOf(event));
					}
					break;
					
				case Event.RECEIVER_DISCONNECTED:
					if (hasStatus(Status.RECEIVER_CONNECTED) || hasStatus(Status.RECEIVER_SEND_HELLO) || hasStatus(Status.RECEIVER_OK)) {
						status &= event;
					} else {
						throw new IllegalStateException(String.valueOf(event));
					}
					break;
					
				default:
					throw new IllegalArgumentException(String.valueOf(event));
			}
		}
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == null)
            return false;
        if (obj == this)
            return true;
        if (obj.getClass() != getClass())
            return false;
        Member member = (Member) obj;
		return new EqualsBuilder().append(host, member.getHost()).append(port, member.getPort()).isEquals();
	}

	@Override
	public int hashCode() {
		return new HashCodeBuilder().append(host).append(port).build();
	}

	@Override
	public String toString() {
		return "Member [host=" + host + ", port=" + port + ", localUuid="
				+ localUuid + ", remoteUuid=" + remoteUuid + "]";
	}

}
